package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.message.api.client.AsyncSmsClient;
import com.tianji.message.domain.dto.SmsInfoDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.UserCouponQuery;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.tianji.promotion.utils.CodeUtil;
import com.tianji.promotion.utils.MyLock;
import lombok.RequiredArgsConstructor;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author xzh
 * @since 2024-03-20
 */
@Service
@RequiredArgsConstructor
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {
    private final CouponMapper couponMapper;
    private final IExchangeCodeService codeService;
    private final AsyncSmsClient asyncSmsClient;
    private final UserClient userClient;
    private final StringRedisTemplate redisTemplate;

    private final RabbitMqHelper mqHelper;


    /**
     * 用户领取优惠券
     *
     * @param id
     */
    @Override
    public void saveUserCouponById(Long id) {

        // 执行任务

        Coupon coupon = couponMapper.selectById(id);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        //2.判断优惠券是否过期 或者 没到开始日期
        LocalDateTime now = LocalDateTime.now();
        if (now.isAfter(coupon.getIssueEndTime()) || now.isBefore(coupon.getIssueBeginTime())) {
            throw new BadRequestException("优惠券没到开始日期或已过结束日期");
        }
        //3.判断优惠券还有没有剩余
        if (coupon.getIssueNum() >= coupon.getTotalNum()) {
            throw new BadRequestException("优惠券已领完,再看看其他的吧");
        }
        //4.判断用户领取该优惠券是否达到上限
        List<UserCoupon> list = lambdaQuery().eq(UserCoupon::getCouponId, id)
                .eq(UserCoupon::getUserId, UserContext.getUser()).list();
        if (list != null && list.size() >= coupon.getUserLimit()) {
            throw new BadRequestException("该优惠券已领到上限,不可重复领取");
        }
        //判断完成 用户可以领取
        //5.修改coupon表 增加已发行数量
        int result = couponMapper.updateIssuseNumById(id);
        if (result == 0) {
            throw new DbException("优惠券库存不足");
        }
        Long userId = UserContext.getUser();
        //userId.toString().intern()  因为string对象都是new出来的 比较地址值
        // 加上intern()可以让他去线程池找相同的字段的直接用则地址相同

        // 执行任务
        IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
        userCouponService.saveUserCoupon(coupon);


    }

    //此注解说明此方法为中间方法
    @MyLock(name = "lock:coupon:#{T(com.tianji.common.utils.UserContext).getUser()}")
    @Transactional
    @Override
    public void saveUserCoupon(Coupon coupon) {
        //6.在user_coupon表增加领取记录
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(UserContext.getUser());
        userCoupon.setCouponId(coupon.getId());


        //状态
        userCoupon.setStatus(UserCouponStatus.UNUSED);
        //创建时间
        LocalDateTime now = LocalDateTime.now();
        userCoupon.setCreateTime(now);
        //有效开始时间和结束时间
        if (coupon.getTermDays() == null) {
            userCoupon.setTermBeginTime(coupon.getTermBeginTime());
            userCoupon.setTermEndTime(coupon.getTermEndTime());
        }
        userCoupon.setTermBeginTime(now);
        userCoupon.setTermEndTime(now.plusDays(coupon.getTermDays()));
        save(userCoupon);
    }

    /**
     * 用户兑换兑换码
     *
     * @param code
     */
    @Override
    public void saveUserExchangeCodeById(String code) {
        //逻辑
        //1.将兑换码反解析 看看兑换码是否正确
        long serialNum = CodeUtil.parseCode(code);
        //2.利用BitPluse 查看数据库看看兑换码是否已被使用
        boolean exchanged = codeService.updateExchangeMake(serialNum, true);
        if (exchanged) {
            throw new BadRequestException("兑换码已被兑换过了");
        }

        //3.查询数据库 判断兑换码是否存在 是否已过期或未开始
        ExchangeCode exchangeCode = codeService.lambdaQuery().eq(ExchangeCode::getCode, code).one();
        if (exchangeCode == null) {
            throw new BizIllegalException("兑换码不存在！");
        }
        LocalDateTime now = LocalDateTime.now();
        if (now.isAfter(exchangeCode.getExpiredTime())) {
            throw new BizIllegalException("兑换码已经过期");
        }
        //4.查看user_coupon表中本用户的兑换数量是否已到上限
        List<UserCoupon> userCouponList = lambdaQuery()
                .eq(UserCoupon::getCouponId, exchangeCode.getExchangeTargetId()).list();

        Coupon coupon = couponMapper.selectById(exchangeCode.getExchangeTargetId());
        if (userCouponList.size() >= coupon.getUserLimit()) {
            throw new BizIllegalException("以达到领取上限");
        }
        //5.修改兑换码状态为已使用 添加使用人 已发放+1
        //添加乐观锁  因为需要将事务注解拿下来消除
        Long userId = UserContext.getUser();
        ExchangeCodeStatus status = ExchangeCodeStatus.USED;
        ExchangeCodeStatus oldStatus = ExchangeCodeStatus.UNUSED;
        codeService.happyLockUpdateCodeById(userId, status, oldStatus, serialNum);

        //5.2已发放+1  添加乐观锁
        coupon.setIssueNum(coupon.getIssueNum() + 1);
        couponMapper.updateById(coupon);
        //6.在user_coupon表中增加数据
        //6.1创建锁对象

        //获取锁成功执行任务  微服务 多实例部署使用的锁
        IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
        userCouponService.saveUserCoupon(coupon);

    }

    /**
     * 分页查询我的优惠券
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<CouponVO> queryUserCouponPage(UserCouponQuery query) {
        //1.根据状态查出我的优惠券
        Long userId = UserContext.getUser();
        Page<UserCoupon> page = lambdaQuery()
                .eq(UserCoupon::getStatus, query.getStatus())
                .eq(UserCoupon::getUserId, userId)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        //2.获得页面资源
        List<UserCoupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        //3.获得优惠券id集合
        List<Long> couponIds = records.stream()
                .map(UserCoupon::getCouponId).collect(Collectors.toList());
        //4.查询优惠券详情
        List<Coupon> coupons = couponMapper.selectBatchIds(couponIds);
        if (CollUtils.isEmpty(coupons)) {
            return PageDTO.empty(0L, 0L);
        }
        List<CouponVO> list = new ArrayList<>(records.size());
        //5.转成map集合 获得过期时间
        Map<Long, LocalDateTime> map = records.stream()
                .collect(Collectors.toMap(UserCoupon::getCouponId, UserCoupon::getTermEndTime));
        for (Coupon coupon : coupons) {
            //6.拷贝
            CouponVO vo = BeanUtils.copyBean(coupon, CouponVO.class);
            vo.setSpecific(coupon.getLimitation());
            vo.setName(coupon.getCuoponName());

            vo.setTermEndTime(map.get(coupon.getId()));
            list.add(vo);
        }
        //7.封装返回
        return PageDTO.of(page, list);
    }

    /**
     * 在优惠券即将过期前以短信方式提醒用户。每3天执行一次 在早上9点
     */
    @Override
    public void taskTextingMessageByRabbitMq() {
        //1.获得未使用未过期的所有优惠券
        List<UserCoupon> userCouponList = lambdaQuery()
                .eq(UserCoupon::getStatus, UserCouponStatus.UNUSED).list();
        if (CollUtils.isEmpty(userCouponList)) {
            return;
        }
        //2.判断如果过期时间小于5天则进行短信提醒 获得小于5天的userId
        LocalDateTime now = LocalDateTime.now();
        List<UserCoupon> minUserCoupon = userCouponList.stream()
                .filter(uc -> uc.getTermEndTime().minusDays(5).isBefore(now))
                .collect(Collectors.toList());
        //3.查询用户注册手机号码
        List<Long> couponIds = minUserCoupon.stream()
                .map(UserCoupon::getCouponId).collect(Collectors.toList());

        List<Long> userIds = minUserCoupon.stream()
                .map(UserCoupon::getUserId).collect(Collectors.toList());
        List<UserDTO> userDTOS = userClient.queryUserByIds(userIds);
        List<String> phones = userDTOS.stream()
                .map(UserDTO::getCellPhone).collect(Collectors.toList());

        //4.发送mq异步通知 todo
        SmsInfoDTO dto = new SmsInfoDTO();
        dto.setPhones(phones);
        dto.setTemplateCode("1");
        asyncSmsClient.sendMessage(dto);
    }
}
